package com.aliyun.messagemgt.application.service.Impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.messagemgt.common.dto.send.MsgBody;
import com.aliyun.messagemgt.common.dto.send.MsgRecordVO;
import com.aliyun.messagemgt.common.dto.send.PushChannelInfo;
import com.aliyun.messagemgt.application.service.PushChannelMap;
import com.aliyun.messagemgt.application.service.TokenUtil;
import com.aliyun.messagemgt.application.service.WeixinMediaHandler;
import com.aliyun.messagemgt.application.service.utils.MediaTypeUtil;
import com.aliyun.messagemgt.common.enums.MediaLimitEnum;
import com.aliyun.messagemgt.common.constants.push.MediaHandlerCodeConstant;
import com.aliyun.messagemgt.common.constants.push.WeChatHandlerCodeConstant;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;

/**
 * @Author: Xiaohang Lv
 * @Date: 2019/5/15 18:02
 */
@Component
public class WeixinMediaHandlerImpl implements WeixinMediaHandler {

    private static final Logger logger = LoggerFactory.getLogger(WeixinMediaHandlerImpl.class);

    //@Value("${wechat.upload.url}")
    private String uploadUrl;

    @Autowired
    private PushChannelMap pushChannelMap;
    @Autowired
    private TokenUtil tokenUtil;

    @Override
    public Integer uploadTemporary(MsgBody msgBody, MsgRecordVO msgRecordVO) {
        Integer res = null;
        PushChannelInfo pushChannelInfo = pushChannelMap.getChannelInfoByChannelId(Long.valueOf(msgRecordVO.getPushChannelId()));
        String token = tokenUtil.getToken(pushChannelInfo);
        String requestUrl = uploadUrl + token + "&type=" + msgBody.getType().toLowerCase();
        res = getMediaIdFromUrl(requestUrl, msgBody);
        return res;
    }

    /**
     * 网络多媒体上传到微信服务器
     *
     * @param msgBody content为图片路径
     * @return
     * @throws Exception
     */
    public Integer getMediaIdFromUrl(String requestUrl, MsgBody msgBody) {
        Integer res = null;
        String wechatResult = null;

        HttpURLConnection mediaConnection = null;

        // 获取网络多媒体
        try {
            URL mediaUrl = new URL(msgBody.getContent());
            if (msgBody.getContent().startsWith("https")) {
                // MediaUrlHandlerImpl.trustAllHttpsCertificates();
                HostnameVerifier hv = new HostnameVerifier() {
                    @Override
                    public boolean verify(String urlHostName, SSLSession session) {
                        return true;
                    }
                };
                HttpsURLConnection.setDefaultHostnameVerifier(hv);
                // 创建链接
                mediaConnection = (HttpsURLConnection) mediaUrl.openConnection();
                mediaConnection.setRequestMethod("GET");
                mediaConnection.setConnectTimeout(10000);
            } else {
                mediaConnection = (HttpURLConnection) mediaUrl.openConnection();
            }
            mediaConnection.setDoOutput(true);
            mediaConnection.setRequestMethod("GET");
        } catch (Exception e) {
            logger.error("获取微信需要上传的文件的流失败：{}", e.toString());
            res = WeChatHandlerCodeConstant.URL_IO_ERROR;
            return res;
        }

        // 判断类型是否合法
        // 获取类型，application开头或者null才考虑guessContentTypeFromName
        String contentType = mediaConnection.getContentType();
        String type = MediaTypeUtil.getType(msgBody, contentType);
        if (type == null || type.startsWith("application")) {
            contentType = HttpURLConnection.guessContentTypeFromName(msgBody.getContent());
            type = MediaTypeUtil.getType(msgBody, contentType);
        }
        if (!checkType(msgBody, type)) {
            logger.error("上传的文件类型微信不支持，目前文件类型为 {} ", type);
            res = WeChatHandlerCodeConstant.WECHAT_INVALID_TYPE;
            return res;
        }

        // 获取文件大小，判断文件大小是否合法
        Integer length = mediaConnection.getContentLength();
        if (!checkLength(msgBody, length)) {
            logger.error("上传的文件大小微信不支持，目前文件大小为 {} byte", length);
            res = WeChatHandlerCodeConstant.WECHAT_INVALID_LENGTH;
            return res;
        }

        // 合法后，拼接文件名
        if ("jpeg".equals(type.toLowerCase())) {
            type = "jpg";
            contentType = "image/jpg";
        }
        String fileName = generateCodeByUuid() + "." + type;

        HttpURLConnection uploadConnection = null;
        try {
            // 建立微信服务器http连接
            URL uploadUrl = new URL(requestUrl);
            uploadConnection = (HttpURLConnection) uploadUrl.openConnection();
            uploadConnection.setRequestMethod("POST");
            uploadConnection.setDoInput(true);
            uploadConnection.setDoOutput(true);
            uploadConnection.setUseCaches(false);
            // 设置请求头信息
            uploadConnection.setRequestProperty("Connection", "Keep-Alive");
            uploadConnection.setRequestProperty("Charset", "UTF-8");
            // 设置边界
            String boundary = "----------" + System.currentTimeMillis();
            uploadConnection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
            // 请求正文信息
            StringBuilder sb = new StringBuilder();
            // 必须多两道线
            sb.append("--");
            sb.append(boundary);
            sb.append("\r\n");
            sb.append("Content-Disposition: form-data;name=\"media\";filename=\"" + fileName + "\"\r\n");
            sb.append("Content-Type:" + contentType + "\r\n\r\n");
            //sb.append(boundary);
            byte[] head = sb.toString().getBytes("utf-8");
            // 获得输出流
            OutputStream out = new DataOutputStream(uploadConnection.getOutputStream());
            // 输出表头
            out.write(head);

            // 文件正文部分
            // 把文件已流文件的方式 推入到url中
            DataInputStream in = new DataInputStream(mediaConnection.getInputStream());
            int bytes = 0;
            byte[] bufferOut = new byte[1024];
            while ((bytes = in.read(bufferOut)) != -1) {
                out.write(bufferOut, 0, bytes);
            }
            in.close();

            // 结尾部分
            // 定义最后数据分隔线
            byte[] foot = ("\r\n--" + boundary + "--\r\n").getBytes("utf-8");
            out.write(foot);
            out.flush();
            out.close();
            mediaConnection.disconnect();

            StringBuffer buffer = new StringBuffer();
            BufferedReader reader = null;

            // 定义BufferedReader输入流来读取URL的响应
            reader = new BufferedReader(new InputStreamReader(uploadConnection.getInputStream()));
            String line = null;
            while ((line = reader.readLine()) != null) {
                buffer.append(line);
            }
            if (wechatResult == null) {
                wechatResult = buffer.toString();
            }
            if (reader != null) {
                reader.close();
            }
        } catch (IOException e) {
            logger.info("上传临时多媒体文件出现异常: {}", e.getMessage());
            res = WeChatHandlerCodeConstant.URL_IO_ERROR;
            return res;
        }

        // 处理微信返回结果
        JSONObject jsonObj = JSON.parseObject(wechatResult);
        logger.info("上传临时多媒体文件，微信服务器返回结果: {}", jsonObj);
        // Set Media id
        msgBody.setContent(jsonObj.getString("media_id"));
        res = MediaHandlerCodeConstant.SUCCESS;
        return res;
    }

    /**
     * 生成UUID Code
     *
     * @return
     */
    private static String generateCodeByUuid() {
        UUID uniqueKey = UUID.randomUUID();
        return uniqueKey.toString();
    }

    /**
     * 判断文件类型是否合法
     * 图片（image）：支持JPG,PNG格式
     * 语音（voice）：仅支持AMR格式
     * 视频（video）：支持MP4格式
     * 普通文件（file）：暂无限制
     *
     * @param msgBody
     * @param type
     * @return true: 合法 false：不合法
     */
    private static boolean checkType(MsgBody msgBody, String type) {
        if (StringUtils.isEmpty(type)) {
            return false;
        }

        if (MediaLimitEnum.FILE.name().toLowerCase().equals(msgBody.getType().toLowerCase())) {
            return true;
        }

        // 在获取type时，已经校验了文件类型和格式的对应，此处无需重复校验
        Set<String> checkSet = new HashSet<>(4);
        checkSet.add("jpg");
        checkSet.add("jpeg");
        checkSet.add("png");
        checkSet.add("amr");
        checkSet.add("mp4");
        if (checkSet.contains(type.toLowerCase())) {
            return true;
        }
        return false;
    }

    /**
     * 判断文件大小是否合法
     * 图片（image）：2MB
     * 语音（voice）：2MB，播放长度不超过60s
     * 视频（video）：10MB
     * 普通文件（file）：20MB
     *
     * @param msgBody
     * @param length
     * @return true: 合法 false：不合法
     */
    private static boolean checkLength(MsgBody msgBody, int length) {
        Map<String, Integer> checkMap = new HashMap<>(4);
        checkMap.put(MediaLimitEnum.IMAGE.name().toLowerCase(), 2 * 1024 * 1024);
        checkMap.put(MediaLimitEnum.VOICE.name().toLowerCase(), 2 * 1024 * 1024);
        checkMap.put(MediaLimitEnum.VIDEO.name().toLowerCase(), 10 * 1024 * 1024);
        checkMap.put(MediaLimitEnum.FILE.name().toLowerCase(), 20 * 1024 * 1024);

        if (checkMap.get(msgBody.getType().toLowerCase()) >= length) {
            return true;
        }

        return false;
    }
}
